// 聊天@人功能封装(基于Jquery)
(function () {

    var inputElement = null;            // 输入dom对象
    var atFixedElement = null;          // 辅助at pop定位的对象
    var atMemberListElement = null;     // at列表pop 的对象
    var atMemberList = [];              // @的对象列表, ["张三", "李四", "王武", ...]
    var isRun = false;                  // at功能是否初始化运行

    /**
     * 构建一个视图，把字节数组写到缓存中，索引从0开始，大端字节序
     * @param bytes
     * @returns {DataView}
     * @private
     */
    function _inputValueChang(textarea, hiddenObj, atList, event) {
        // 取值
        var objString = textarea.value;
        if (!objString) {
            // console.warn("输入框没有内容, 忽略下列操作");
            atList.innerHTML = "";
            atList.style.display = 'none';
            return false;
        }
        // 记录光标当前位置
        var cursorPosition = _posCursor(textarea);
        // 光标之前的字符串
        var beforeCursorString = objString.substr(0, cursorPosition);
        // 记录@在光标前出现的最近位置
        var atLocation = beforeCursorString.lastIndexOf("@");
        // 记录光标和光标前最近的@之间的字符串，记为标识符，判断其是否含有空格
        var indexString = objString.substr(atLocation, cursorPosition - atLocation);
        // 记录从开始到光标前最近的@之间的字符串，用来定位at对话框的位置
        var positionString = objString.substr(0, atLocation);

        // at对话框是显示状态, 则进行对应处理
        if (atList.style.display == "block") {
            var key = event.keyCode;
            var listClick = atList.getElementsByTagName("li");
            var len = listClick.length;
            if (key == 40) {    // 向下箭头 更换@对象
                // 将光标定位@对话框出现时的位置  否则在按下向下箭头时  光标会移动到textarea中文本的末尾处
                _cursorHandle(textarea, _getById("chat-at-cursor").value);
                var next = _getLiIndex(listClick, "at-list-item-active") == len - 1 ? -1 : _getLiIndex(listClick, "at-list-item-active");
                for (var i = 0; i < len; i++) {
                    $(listClick[i]).removeClass("at-list-item-active");
                }
                $(listClick[next + 1]).addClass("at-list-item-active");
                (listClick[next + 1]).scrollIntoView();
                return false;
            } else if (key == 38) {         // 向上箭头
                _cursorHandle(textarea, _getById("chat-at-cursor").value);
                var prev = _getLiIndex(listClick, "at-list-item-active") == 0 ? len : _getLiIndex(listClick, "at-list-item-active");
                for (var i = 0; i < len; i++) {
                    $(listClick[i]).removeClass("at-list-item-active");
                }
                $(listClick[prev - 1]).addClass("at-list-item-active");
                (listClick[prev - 1]).scrollIntoView();
                return false;
            } else if (key == 13) {         // enter
                _handleString(_getLiIndex(listClick, "at-list-item-active"), textarea, listClick, atList, objString, atLocation, cursorPosition);
                return false;
            } else if (key == 27) {         // esc
                atList.style.display = "none";
                return false;
            }

            //根据输入 动态显示at对话框中的选项列表
            _dynamicDisplayAtList(indexString, atList, textarea, objString, atLocation, cursorPosition);
        }

        //发现有@ 并且@和光标间没有空格和换行时 进行相应处理
        if (beforeCursorString.indexOf('@') != -1 && indexString.indexOf(' ') == -1
            && indexString.indexOf('\n') == -1) {
            //@开始
            _getById("chat-at-cursor").value = _posCursor(textarea);

            //设置at对象对话框
            //根据输入 动态显示at对话框中的选项列表
            _dynamicDisplayAtList(indexString, atList, textarea, objString, atLocation, cursorPosition);


            //显示at对象的对话框
            hiddenObj.innerHTML = positionString.replace(/\n/g, "<br/>") + '<span id="at">@</span>';
            var at = _getById("at");

            atList.style.left = _getXY(at).left + 2 + 'px';
            atList.style.top = _getXY(at).top - textarea.scrollTop + 18 + 'px';
        } else {
            atList.innerHTML = "";
            atList.style.display = 'none';
        }
    }

    /**
     * 返回obj位置
     * @param obj
     * @returns {{}}
     */
    function _getXY(obj) {
        // getBoundingClientRect()用于获取某个元素相对于视窗的位置集合。集合中有top, right, bottom, left等属性
        var rect = obj.getBoundingClientRect();
        // 获取滑动条位置
        var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
        var scrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft;
        var isIE = !(!document.all) ? 2 : 0;

        var position = {};
        position.left = rect.left - isIE + scrollLeft;
        position.top = rect.top - isIE + scrollTop;

        return position;
    }

    /**
     * 获取具有相应类的li的索引
     * @param arr
     * @param cls
     * @returns {boolean|number}
     */
    function _getLiIndex(arr, cls) {
        for (var i = 0; i < arr.length; i++) {
            if (_hasClass(arr[i], cls) == true) {
                return i;
            }
        }
        return false;
    }

    /**
     * 是否有某个class
     * @param ele
     * @param cls
     * @returns {*}
     * @private
     */
    function _hasClass(ele, cls) {
        //\s 匹配任何空白字符，包括空格、制表符、换页符等等
        // return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
        return $(ele).hasClass("at-list-item-active");
    }

    /**
     * 根据at和光标间的字符串动态筛选at对象列表 只显示包含光标和@间字符串的那些对象
     * @param indexString
     * @param atList
     * @param textarea
     * @param objString
     * @param atLocation
     * @param cursorPosition
     * @private
     */
    function _dynamicDisplayAtList(indexString, atList, textarea, objString, atLocation, cursorPosition) {
        var list = JSON.parse(JSON.stringify(atMemberList));

        if (indexString != undefined && indexString != "" && indexString != "@") {
            //去掉第一个字符
            indexString = indexString.substring(1, indexString.length);
            for (var i = 0; i < list.length; i++) {
                var temp = list[i];
                // var nickname = temp.remarks || temp.name;
                if (temp.name.indexOf(indexString) == -1 && temp.remarks.indexOf(indexString) == -1) {
                    list.splice(i, 1);
                    i--;
                }
            }
        }

        var dom = "";
        if (list != undefined && list.length > 0) {
            dom = "";
            // dom = indexString.length > 1 ? '<li class="at-list-item-title">选择最近@的人或直接输入</li>' : '<li class="at-list-item-title">选择昵称或轻敲空格完成输入</li>';
            for (var i = 0; i < list.length; i++) {
                var item = list[i];
                var nickname = item.remarks || item.name;
                dom += '<li class="at-list-item-content" title="' + nickname + '">' + nickname + '</li>';
            }
            atList.style.display = 'block';
            atList.innerHTML = dom;     //设置@的对象列表
        } else {
            // dom = '<li class="at-list-item-title">无匹配的可选项</li>';
            atList.style.display = 'none';
        }

        var listClick = atList.getElementsByTagName('li');
        if (listClick != undefined && listClick.length > 0) {
            (listClick[0]).scrollIntoView();
            listClick[0].className = 'at-list-item-content at-list-item-active';
            for (var i = 0, len = listClick.length; i < len; i++) {
                //选项的鼠标点击事件
                listClick[i].onclick = (function (i) {
                    return function () {
                        _handleString(i, textarea, listClick, atList, objString, atLocation, cursorPosition);
                    }
                })(i);
            }
        }
    }

    /**
     * enter选择list中某一项时  更新textarea的value
     * @param index
     * @param textarea
     * @param listClick
     * @param atList
     * @param objString
     * @param atLocation
     * @param cursorPosition
     * @private
     */
    function _handleString(index, textarea, listClick, atList, objString, atLocation, cursorPosition) {
        //将textarea分成三块，@之前的area1、@+联系人+' '的area2、光标之后的area3
        var area1 = objString.substr(0, atLocation);
        var area2 = '@' + listClick[index].innerHTML + ' ';//@内容后附加空格标识
        var area3 = objString.substr(cursorPosition, _getLength(objString) - cursorPosition);

        //更新textarea的value值
        textarea.value = area1 + area2 + area3;

        //不再显示at对话框
        atList.innerHTML = "";
        atList.style.display = "none";

        //定位光标
        var position = area1.length + area2.length;
        _cursorHandle(textarea, position);
    }

    /**
     * 统计字符串总长度 中文字符为2 英文字符以及数字为1
     * @param obj
     * @returns {number}
     */
    function _getLength(obj) {
        var realLength = 0;
        var len = obj.length;
        var charCode = -1;

        for (var i = 0; i < len; i++) {
            //charCodeAt方法返回一个整数，代表指定位置字符的Unicode编码。
            charCode = obj.charCodeAt(i);
            if (charCode >= 0 && charCode <= 128) {//英文字符以及数字
                realLength += 1;
            } else {
                realLength += 2;
            }
        }

        return realLength;
    }

    /**
     * 定位光标
     * @param obj
     * @param pos
     */
    function _cursorHandle(obj, pos) {
        if (navigator.appName == "Microsoft Internet Explorer") {
            var range = obj.createTextRange();
            range.move('character', pos);
            range.select();
        } else {
            obj.setSelectionRange(pos, pos);
            obj.focus();
        }
    }

    /**
     * 获取光标当前位置
     * @param obj           输入对象
     * @returns {number}
     * @private
     */
    function _posCursor(obj) {
        var isIE = !(!document.all);
        var end = 0;
        if (isIE) {
            //获取用户选择区域 sTextRange.text返回选择的文本
            var sTextRange = document.selection.createRange();
            if (sTextRange.parentElement() == obj) {//光标在此obj对象中
                var oTextRange = document.body.createTextRange();
                //moveToElementText 移动oTextRange到指定对象
                oTextRange.moveToElementText(obj);
                //compareEndPoints 比较两个textRange对象的开始点和结束点的位置 startToEnd：比较当前TextRange的开始点与rangeToCompare的结束点。
                for (end = 0; oTextRange.compareEndPoints('StartToEnd', sTextRange) < 0; end++) {
                    //移动一个字符
                    oTextRange.moveStart('character', 1);
                }
                for (var i = 0; i < end; i++) {
                    if (obj.value.charAt(i) == "\n") {
                        end++;
                    }
                }
            }
        } else {
            end = obj.selectionEnd;
        }

        return end;
    }


    /**
     * 根据id选择器获取dom
     * @param eleId
     * @returns {HTMLElement}
     */
    function _getById(element_id) {
        return document.getElementById(element_id);
    }

    /**
     * 监听滚轮, 如果列表是显示状态那么就滚动
     */
    function _windowAddMouseWheel() {
        var scrollFunc = function (e) {
            e = e || window.event;
            if (e.wheelDelta) {  //判断浏览器IE，谷歌滑轮事件
                if (e.wheelDelta > 0 && !window.is_onload_history) { //当滑轮向上滚动时
                    // console.info('当滑轮向上滚动时');
                    $("#chat-at-member-list").scrollTop($("#chat-at-member-list").scrollTop() - 4);
                } else {
                    // console.info('当滑轮向下滚动时');
                    $("#chat-at-member-list").scrollTop($("#chat-at-member-list").scrollTop() + 4);
                }
            } else if (e.detail) {  //Firefox滑轮事件
                if (e.detail > 0 && !window.is_onload_history) { //当滑轮向上滚动时
                    // console.info('当滑轮向上滚动时');
                    $("#chat-at-member-list").scrollTop($("#chat-at-member-list").scrollTop() - 4);
                } else {
                    // console.info('当滑轮向下滚动时');
                    $("#chat-at-member-list").scrollTop($("#chat-at-member-list").scrollTop() + 4);
                }
            }
        };
        //给页面绑定滑轮滚动事件
        if (document.addEventListener) {
            document.addEventListener('DOMMouseScroll', scrollFunc, false);
        }
        //滚动滑轮触发scrollFunc方法
        window.onmousewheel = document.onmousewheel = scrollFunc;
    }

    // 开放对象
    var obj = {
        /**
         * 初始化at
         * @param input_element         文本输入对象, id="xxx"
         * @param at_member_list        @的对象列表, ["张三", "李四", "王武", ...]
         * @returns {boolean}
         */
        initAt: function (input_element, at_member_list, at_member_list_max_high = "120") {
            var input_element_obj = _getById(input_element);
            if ((!input_element || !input_element_obj) || (typeof at_member_list != "object" || at_member_list.length < 1)) {
                console.warn("初始化聊天@功能失败, 参数不正确, 输入对象=" + input_element + ", @成员列表=" + JSON.stringify(at_member_list));
                return false;
            }
            //先进性复位
            this.unsetAt();
            // 开始初始化
            isRun = true;
            inputElement = input_element_obj;
            atMemberList = at_member_list;
            atMemberListElement = _getById("chat-at-member-list");
            atFixedElement = _getById("chat-at-input-fixed");
            // 增加at列表盒子
            if (!atMemberListElement) {
                $("body").append("<ul id='chat-at-member-list' class='at-list-style' style='max-height: " + at_member_list_max_high + "px;'></ul>");
            }

            // at输入光标定位
            if (!_getById("chat-at-cursor")) {
                $("body").append("<input id='chat-at-cursor' type='hidden' />");
            }

            // 增加at定位盒子, 跟input_element相同 样式属性
            if (!atFixedElement) {
                var input_element_class = $(input_element_obj).attr("class");
                var input_element_parent = $(input_element_obj).parent();
                input_element_parent.append("<div id='chat-at-input-fixed' contenteditable='true'></div>");
                if (input_element_class) {
                    $("#chat-at-input-fixed").attr("class", input_element_class);
                }
                // at辅助定位div样式需要与textarea完全一样 位置也一样 textareaz的z-index更高一些  用于计算at对象列表框弹出的位置
                var input_element_z_index = $(input_element_obj).css("z-index");
                $("#chat-at-input-fixed").css("z-index", input_element_z_index == "auto" ? "-1" : input_element_z_index - 1);
            }

            atMemberListElement = _getById("chat-at-member-list");
            atFixedElement = _getById("chat-at-input-fixed");

            // 增加键盘和鼠标监听(输入/粘贴@)
            $(input_element_obj).bind('keyup', function (event) {
                _inputValueChang(inputElement, atFixedElement, atMemberListElement, event);
            });
            $(input_element_obj).bind('mouseup', function (event) {
                _inputValueChang(inputElement, atFixedElement, atMemberListElement, event);
            });

            // 监听滚轮滚动
            _windowAddMouseWheel();

            // 点击空白处的事件处理（希望点空白，能自动隐藏pop层的显示）
            $("body").click(function (event) {
                $(atMemberListElement).hide();
            });
        },

        /**
         * 获取at信息
         * 获取以start作为开始 以end作为结尾的所有字符串
         *  i （ignore case）执行对大小写不敏感的匹配。
         g （global search）执行全局匹配（查找所有匹配而非在找到第一个匹配后停止）。
         m （multiline）执行多行匹配。
         a.*b，它将会匹配最长的以a开始，以b结束的字符串
         a.*?b 匹配最短的，以a开始，以b结束的字符串 ?涉及到贪婪模式
         获取source中以start作为开始 以end作为结尾的所有字符串
         * @param source        文本内容
         * @param start         开始标志
         * @param end           结束标志
         * @returns {*}
         */
        getAtData: function () {
            if (!inputElement || !inputElement.value || atMemberList.length < 1) {
                return false;
            }
            var source = inputElement.value;
            var start = "@";
            var end = " ";
            var oReg = new RegExp(start + '.*?' + end, 'img');
            var oRegStart = new RegExp(start, 'g');
            var oRegEnd = new RegExp(end, 'g');

            try {
                var at_list = source.match(oReg)
                    .join("=")
                    .replace(oRegStart, "")
                    .replace(oRegEnd, "")
                    .split("=");
                var result_list = [];
                for (var i = 0; i < at_list.length; i++) {
                    var item_i = at_list[i];
                    for (var j = 0; j < atMemberList.length; j++) {
                        var item_j = atMemberList[j];
                        if (item_i == item_j.name || item_i == item_j.remarks) {
                            result_list.unshift(item_j);
                        }
                    }

                }
                return result_list;
            } catch (e) {
                return false;
            }
        },

        /**
         * 复位at
         * @returns {boolean}
         */
        unsetAt: function () {
            if (!inputElement || !atMemberListElement) return false;
            atMemberListElement.style.display = "none";
            // 增加键盘和鼠标监听(输入/粘贴@)
            $(inputElement).unbind('keyup');
            $(inputElement).unbind('mouseup');

            inputElement = null;            // 输入dom对象
            atFixedElement = null;          // 辅助at pop定位的对象
            atMemberListElement = null;     // at列表pop 的对象
            atMemberList = [];              // @的对象列表, ["张三", "李四", "王武", ...]
            isRun = false;                  // at功能是否初始化运行
            return true;
        },

        /**
         * at是否正在监听运行
         * @returns {boolean}
         */
        atIsRun: function () {
            return isRun;
        },

        /**
         * 检测at的弹层是否正在显示
         * @returns {boolean}
         */
        atPopisShow: function () {
            if (!atMemberListElement) {
                return false;
            }
            if (atMemberListElement.style.display == "block") {
                return true;
            }
            return false;
        }

    };

    window.ChatAt = obj;
})();